home *** CD-ROM | disk | FTP | other *** search
/ Linux Cubed Series 3: Developer Tools / Linux Cubed Series 3 - Developer Tools.iso / devel / ver_cont / cvs-1.8 / cvs-1 / cvs-1.8.1 / src / recurse.c < prev    next >
Encoding:
C/C++ Source or Header  |  1996-05-06  |  18.4 KB  |  715 lines

  1. /*
  2.  * Copyright (c) 1992, Brian Berliner and Jeff Polk
  3.  * 
  4.  * You may distribute under the terms of the GNU General Public License as
  5.  * specified in the README file that comes with the CVS 1.4 kit.
  6.  * 
  7.  * General recursion handler
  8.  * 
  9.  */
  10.  
  11. #include "cvs.h"
  12. #include "savecwd.h"
  13. #include "fileattr.h"
  14. #include "edit.h"
  15.  
  16. static int do_dir_proc PROTO((Node * p, void *closure));
  17. static int do_file_proc PROTO((Node * p, void *closure));
  18. static void addlist PROTO((List ** listp, char *key));
  19. static int unroll_files_proc PROTO((Node *p, void *closure));
  20. static void addfile PROTO((List **listp, char *dir, char *file));
  21.  
  22.  
  23. /*
  24.  * Local static versions eliminates the need for globals
  25.  */
  26. static FILEPROC fileproc;
  27. static FILESDONEPROC filesdoneproc;
  28. static DIRENTPROC direntproc;
  29. static DIRLEAVEPROC dirleaveproc;
  30. static int which;
  31. static Dtype flags;
  32. static int aflag;
  33. static int readlock;
  34. static int dosrcs;
  35. static char update_dir[PATH_MAX];
  36. static char *repository = NULL;
  37. static List *filelist = NULL; /* holds list of files on which to operate */
  38. static List *dirlist = NULL; /* holds list of directories on which to operate */
  39.  
  40. struct recursion_frame {
  41.   FILEPROC fileproc;
  42.   FILESDONEPROC filesdoneproc;
  43.   DIRENTPROC direntproc;
  44.   DIRLEAVEPROC dirleaveproc;
  45.   Dtype flags;
  46.   int which;
  47.   int aflag;
  48.   int readlock;
  49.   int dosrcs;
  50. };
  51.  
  52. /*
  53.  * Called to start a recursive command.
  54.  *
  55.  * Command line arguments dictate the directories and files on which
  56.  * we operate.  In the special case of no arguments, we default to
  57.  * ".".
  58.  *
  59.  * The general algorithm is as follows.
  60.  */
  61. int
  62. start_recursion (fileproc, filesdoneproc, direntproc, dirleaveproc,
  63.          argc, argv, local, which, aflag, readlock,
  64.          update_preload, dosrcs, wd_is_repos)
  65.     FILEPROC fileproc;
  66.     FILESDONEPROC filesdoneproc;
  67.     DIRENTPROC     direntproc;
  68.     DIRLEAVEPROC dirleaveproc;
  69.     int argc;
  70.     char **argv;
  71.     int local;
  72.     int which;
  73.     int aflag;
  74.     int readlock;
  75.     char *update_preload;
  76.     int dosrcs;
  77.     int wd_is_repos;    /* Set if caller has already cd'd to the repository */
  78. {
  79.     int i, err = 0;
  80.     Dtype flags;
  81.     List *files_by_dir = NULL;
  82.     struct recursion_frame frame;
  83.  
  84.     expand_wild (argc, argv, &argc, &argv);
  85.  
  86.     if (update_preload == NULL)
  87.     update_dir[0] = '\0';
  88.     else
  89.     (void) strcpy (update_dir, update_preload);
  90.  
  91.     if (local)
  92.     flags = R_SKIP_DIRS;
  93.     else
  94.     flags = R_PROCESS;
  95.  
  96.     /* clean up from any previous calls to start_recursion */
  97.     if (repository)
  98.     {
  99.     free (repository);
  100.     repository = (char *) NULL;
  101.     }
  102.     if (filelist)
  103.     dellist (&filelist); /* FIXME-krp: no longer correct. */
  104. /* FIXME-krp: clean up files_by_dir */
  105.     if (dirlist)
  106.     dellist (&dirlist);
  107.  
  108.     if (argc == 0)
  109.     {
  110.  
  111.     /*
  112.      * There were no arguments, so we'll probably just recurse. The
  113.      * exception to the rule is when we are called from a directory
  114.      * without any CVS administration files.  That has always meant to
  115.      * process each of the sub-directories, so we pretend like we were
  116.      * called with the list of sub-dirs of the current dir as args
  117.      */
  118.     if ((which & W_LOCAL) && !isdir (CVSADM))
  119.         dirlist = Find_Directories ((char *) NULL, W_LOCAL);
  120.     else
  121.         addlist (&dirlist, ".");
  122.  
  123.     err += do_recursion (fileproc, filesdoneproc, direntproc,
  124.                 dirleaveproc, flags, which, aflag,
  125.                 readlock, dosrcs);
  126.     return(err);
  127.     }
  128.  
  129.  
  130.     /*
  131.      * There were arguments, so we have to handle them by hand. To do
  132.      * that, we set up the filelist and dirlist with the arguments and
  133.      * call do_recursion.  do_recursion recognizes the fact that the
  134.      * lists are non-null when it starts and doesn't update them.
  135.      *
  136.      * explicitly named directories are stored in dirlist.
  137.      * explicitly named files are stored in filelist.
  138.      * other possibility is named entities whicha are not currently in
  139.      * the working directory.
  140.      */
  141.     
  142.     for (i = 0; i < argc; i++)
  143.     {
  144.     /* if this argument is a directory, then add it to the list of
  145.        directories. */
  146.  
  147.     if (!wrap_name_has (argv[i], WRAP_TOCVS) && isdir (argv[i]))
  148.         addlist (&dirlist, argv[i]);
  149.     else
  150.     {
  151.         /* otherwise, split argument into directory and component names. */
  152.         char *dir;
  153.         char *comp;
  154.         char tmp[PATH_MAX];
  155.         char *file_to_try;
  156.  
  157.         /* Now break out argv[i] into directory part (DIR) and file part (COMP).
  158.            DIR and COMP will each point to a newly malloc'd string.  */
  159.         dir = xstrdup (argv[i]);
  160.         comp = last_component (dir);
  161.         if (comp == dir)
  162.         {
  163.         /* no dir component.  What we have is an implied "./" */
  164.         dir = xstrdup(".");
  165.         }
  166.         else
  167.         {
  168.         char *p = comp;
  169.  
  170.         p[-1] = '\0';
  171.         comp = xstrdup (p);
  172.         }
  173.  
  174.         /* if this argument exists as a file in the current
  175.            working directory tree, then add it to the files list.  */
  176.  
  177.         if (wd_is_repos)
  178.         {
  179.         /* If doing rtag, we've done a chdir to the repository. */
  180.         sprintf (tmp, "%s%s", argv[i], RCSEXT);
  181.         file_to_try = tmp;
  182.         }
  183.         else
  184.           file_to_try = argv[i];
  185.  
  186.         if(isfile(file_to_try))
  187.         addfile (&files_by_dir, dir, comp);
  188.         else if (isdir (dir))
  189.         {
  190.         if (isdir (CVSADM))
  191.         {
  192.             /* otherwise, look for it in the repository. */
  193.             char *save_update_dir;
  194.             char *repos;
  195.         
  196.             /* save & set (aka push) update_dir */
  197.             save_update_dir = xstrdup (update_dir);
  198.  
  199.             if (*update_dir != '\0')
  200.             (void) strcat (update_dir, "/");
  201.  
  202.             (void) strcat (update_dir, dir);
  203.         
  204.             /* look for it in the repository. */
  205.             repos = Name_Repository (dir, update_dir);
  206.             (void) sprintf (tmp, "%s/%s", repos, comp);
  207.             free (repos);
  208.  
  209.             if (!wrap_name_has (comp, WRAP_TOCVS) && isdir(tmp))
  210.             addlist (&dirlist, argv[i]);
  211.             else
  212.             addfile (&files_by_dir, dir, comp);
  213.  
  214.             (void) sprintf (update_dir, "%s", save_update_dir);
  215.             free (save_update_dir);
  216.         }
  217.         else
  218.             addfile (&files_by_dir, dir, comp);
  219.         }
  220.         else
  221.         error (1, 0, "no such directory `%s'", dir);
  222.  
  223.         free (dir);
  224.         free (comp);
  225.     }
  226.     }
  227.  
  228.     /* At this point we have looped over all named arguments and built
  229.        a coupla lists.  Now we unroll the lists, setting up and
  230.        calling do_recursion. */
  231.  
  232.     frame.fileproc = fileproc;
  233.     frame.filesdoneproc = filesdoneproc;
  234.     frame.direntproc = direntproc;
  235.     frame.dirleaveproc = dirleaveproc;
  236.     frame.flags = flags;
  237.     frame.which = which;
  238.     frame.aflag = aflag;
  239.     frame.readlock = readlock;
  240.     frame.dosrcs = dosrcs;
  241.     err += walklist (files_by_dir, unroll_files_proc, (void *) &frame);
  242.  
  243.     /* then do_recursion on the dirlist. */
  244.     if (dirlist != NULL)
  245.     err += do_recursion (frame.fileproc, frame.filesdoneproc,
  246.                  frame.direntproc, frame.dirleaveproc,
  247.                  frame.flags, frame.which, frame.aflag,
  248.                  frame.readlock, frame.dosrcs);
  249.  
  250.     /* Free the data which expand_wild allocated.  */
  251.     for (i = 0; i < argc; ++i)
  252.     free (argv[i]);
  253.     free (argv);
  254.  
  255.     return (err);
  256. }
  257.  
  258. /*
  259.  * Implement the recursive policies on the local directory.  This may be
  260.  * called directly, or may be called by start_recursion
  261.  */
  262. int
  263. do_recursion (xfileproc, xfilesdoneproc, xdirentproc, xdirleaveproc,
  264.           xflags, xwhich, xaflag, xreadlock, xdosrcs)
  265.     FILEPROC xfileproc;
  266.     FILESDONEPROC xfilesdoneproc;
  267.     DIRENTPROC xdirentproc;
  268.     DIRLEAVEPROC xdirleaveproc;
  269.     Dtype xflags;
  270.     int xwhich;
  271.     int xaflag;
  272.     int xreadlock;
  273.     int xdosrcs;
  274. {
  275.     int err = 0;
  276.     int dodoneproc = 1;
  277.     char *srepository;
  278.     List *entries = NULL;
  279.  
  280.     /* do nothing if told */
  281.     if (xflags == R_SKIP_ALL)
  282.     return (0);
  283.  
  284.     /* set up the static vars */
  285.     fileproc = xfileproc;
  286.     filesdoneproc = xfilesdoneproc;
  287.     direntproc = xdirentproc;
  288.     dirleaveproc = xdirleaveproc;
  289.     flags = xflags;
  290.     which = xwhich;
  291.     aflag = xaflag;
  292.     readlock = noexec ? 0 : xreadlock;
  293.     dosrcs = xdosrcs;
  294.  
  295.     /* The fact that locks are not active here is what makes us fail to have
  296.        the
  297.  
  298.            If someone commits some changes in one cvs command,
  299.        then an update by someone else will either get all the
  300.        changes, or none of them.
  301.  
  302.        property (see node Concurrency in cvs.texinfo).
  303.  
  304.        The most straightforward fix would just to readlock the whole
  305.        tree before starting an update, but that means that if a commit
  306.        gets blocked on a big update, it might need to wait a *long*
  307.        time.
  308.  
  309.        A more adequate fix would be a two-pass design for update,
  310.        checkout, etc.  The first pass would go through the repository,
  311.        with the whole tree readlocked, noting what versions of each
  312.        file we want to get.  The second pass would release all locks
  313.        (except perhaps short-term locks on one file at a
  314.        time--although I think RCS already deals with this) and
  315.        actually get the files, specifying the particular versions it wants.
  316.  
  317.        This could be sped up by separating out the data needed for the
  318.        first pass into a separate file(s)--for example a file
  319.        attribute for each file whose value contains the head revision
  320.        for each branch.  The structure should be designed so that
  321.        commit can relatively quickly update the information for a
  322.        single file or a handful of files (file attributes, as
  323.        implemented in Jan 96, are probably acceptable; improvements
  324.        would be possible such as branch attributes which are in
  325.        separate files for each branch).  */
  326.  
  327. #if defined(SERVER_SUPPORT) && defined(SERVER_FLOWCONTROL)
  328.     /*
  329.      * Now would be a good time to check to see if we need to stop
  330.      * generating data, to give the buffers a chance to drain to the
  331.      * remote client.  We should not have locks active at this point.
  332.      */
  333.     if (server_active
  334.     /* If there are writelocks around, we cannot pause here.  */
  335.     && (readlock || noexec))
  336.     server_pause_check();
  337. #endif
  338.  
  339.     /*
  340.      * Fill in repository with the current repository
  341.      */
  342.     if (which & W_LOCAL)
  343.     {
  344.     if (isdir (CVSADM))
  345.         repository = Name_Repository ((char *) NULL, update_dir);
  346.     else
  347.         repository = NULL;
  348.     }
  349.     else
  350.     {
  351.     repository = xmalloc (PATH_MAX);
  352.     (void) getwd (repository);
  353.     }
  354.     srepository = repository;        /* remember what to free */
  355.  
  356.     fileattr_startdir (repository);
  357.  
  358.     /*
  359.      * The filesdoneproc needs to be called for each directory where files
  360.      * processed, or each directory that is processed by a call where no
  361.      * directories were passed in.  In fact, the only time we don't want to
  362.      * call back the filesdoneproc is when we are processing directories that
  363.      * were passed in on the command line (or in the special case of `.' when
  364.      * we were called with no args
  365.      */
  366.     if (dirlist != NULL && filelist == NULL)
  367.     dodoneproc = 0;
  368.  
  369.     /*
  370.      * If filelist or dirlist is already set, we don't look again. Otherwise,
  371.      * find the files and directories
  372.      */
  373.     if (filelist == NULL && dirlist == NULL)
  374.     {
  375.     /* both lists were NULL, so start from scratch */
  376.     if (fileproc != NULL && flags != R_SKIP_FILES)
  377.     {
  378.         int lwhich = which;
  379.  
  380.         /* be sure to look in the attic if we have sticky tags/date */
  381.         if ((lwhich & W_ATTIC) == 0)
  382.         if (isreadable (CVSADM_TAG))
  383.             lwhich |= W_ATTIC;
  384.  
  385.         /* find the files and fill in entries if appropriate */
  386.         filelist = Find_Names (repository, lwhich, aflag, &entries);
  387.     }
  388.  
  389.     /* find sub-directories if we will recurse */
  390.     if (flags != R_SKIP_DIRS)
  391.         dirlist = Find_Directories (repository, which);
  392.     }
  393.     else
  394.     {
  395.     /* something was passed on the command line */
  396.     if (filelist != NULL && fileproc != NULL)
  397.     {
  398.         /* we will process files, so pre-parse entries */
  399.         if (which & W_LOCAL)
  400.         entries = Entries_Open (aflag);
  401.     }
  402.     }
  403.  
  404.     /* process the files (if any) */
  405.     if (filelist != NULL && fileproc)
  406.     {
  407.     struct file_info finfo_struct;
  408.  
  409.     /* read lock it if necessary */
  410.     if (readlock && repository && Reader_Lock (repository) != 0)
  411.         error (1, 0, "read lock failed - giving up");
  412.  
  413. #ifdef CLIENT_SUPPORT
  414.     /* For the server, we handle notifications in a completely different
  415.        place (server_notify).  For local, we can't do them here--we don't
  416.        have writelocks in place, and there is no way to get writelocks
  417.        here.  */
  418.     if (client_active)
  419.         notify_check (repository, update_dir);
  420. #endif /* CLIENT_SUPPORT */
  421.  
  422.     finfo_struct.repository = repository;
  423.     finfo_struct.update_dir = update_dir;
  424.     finfo_struct.entries = entries;
  425.     /* do_file_proc will fill in finfo_struct.file.  */
  426.  
  427.     /* process the files */
  428.     err += walklist (filelist, do_file_proc, &finfo_struct);
  429.  
  430.     /* unlock it */
  431.     if (readlock)
  432.         Lock_Cleanup ();
  433.  
  434.     /* clean up */
  435.     dellist (&filelist);
  436.     }
  437.  
  438.     if (entries) 
  439.     {
  440.     Entries_Close (entries);
  441.     entries = NULL;
  442.     }
  443.  
  444.     /* call-back files done proc (if any) */
  445.     if (dodoneproc && filesdoneproc != NULL)
  446.     err = filesdoneproc (err, repository, update_dir[0] ? update_dir : ".");
  447.  
  448.     fileattr_write ();
  449.     fileattr_free ();
  450.  
  451.     /* process the directories (if necessary) */
  452.     if (dirlist != NULL)
  453.     err += walklist (dirlist, do_dir_proc, NULL);
  454. #ifdef notdef
  455.     else if (dirleaveproc != NULL)
  456.     err += dirleaveproc(".", err, ".");
  457. #endif
  458.     dellist (&dirlist);
  459.  
  460.     /* free the saved copy of the pointer if necessary */
  461.     if (srepository)
  462.     {
  463.     free (srepository);
  464.     repository = (char *) NULL;
  465.     }
  466.  
  467.     return (err);
  468. }
  469.  
  470. /*
  471.  * Process each of the files in the list with the callback proc
  472.  */
  473. static int
  474. do_file_proc (p, closure)
  475.     Node *p;
  476.     void *closure;
  477. {
  478.     struct file_info *finfo = (struct file_info *)closure;
  479.     int ret;
  480.  
  481.     finfo->file = p->key;
  482.     finfo->fullname = xmalloc (strlen (finfo->file)
  483.                    + strlen (finfo->update_dir)
  484.                    + 2);
  485.     finfo->fullname[0] = '\0';
  486.     if (finfo->update_dir[0] != '\0')
  487.     {
  488.     strcat (finfo->fullname, finfo->update_dir);
  489.     strcat (finfo->fullname, "/");
  490.     }
  491.     strcat (finfo->fullname, finfo->file);
  492.  
  493.     if (dosrcs && repository)
  494.     finfo->rcs = RCS_parse (finfo->file, repository);
  495.     else 
  496.         finfo->rcs = (RCSNode *) NULL;
  497.     ret = fileproc (finfo);
  498.  
  499.     freercsnode(&finfo->rcs);
  500.     free (finfo->fullname);
  501.  
  502.     return (ret);
  503. }
  504.  
  505. /*
  506.  * Process each of the directories in the list (recursing as we go)
  507.  */
  508. static int
  509. do_dir_proc (p, closure)
  510.     Node *p;
  511.     void *closure;
  512. {
  513.     char *dir = p->key;
  514.     char newrepos[PATH_MAX];
  515.     List *sdirlist;
  516.     char *srepository;
  517.     char *cp;
  518.     Dtype dir_return = R_PROCESS;
  519.     int stripped_dot = 0;
  520.     int err = 0;
  521.     struct saved_cwd cwd;
  522.  
  523.     /* set up update_dir - skip dots if not at start */
  524.     if (strcmp (dir, ".") != 0)
  525.     {
  526.     if (update_dir[0] != '\0')
  527.     {
  528.         (void) strcat (update_dir, "/");
  529.         (void) strcat (update_dir, dir);
  530.     }
  531.     else
  532.         (void) strcpy (update_dir, dir);
  533.  
  534.     /*
  535.      * Here we need a plausible repository name for the sub-directory. We
  536.      * create one by concatenating the new directory name onto the
  537.      * previous repository name.  The only case where the name should be
  538.      * used is in the case where we are creating a new sub-directory for
  539.      * update -d and in that case the generated name will be correct.
  540.      */
  541.     if (repository == NULL)
  542.         newrepos[0] = '\0';
  543.     else
  544.         (void) sprintf (newrepos, "%s/%s", repository, dir);
  545.     }
  546.     else
  547.     {
  548.     if (update_dir[0] == '\0')
  549.         (void) strcpy (update_dir, dir);
  550.  
  551.     if (repository == NULL)
  552.         newrepos[0] = '\0';
  553.     else
  554.         (void) strcpy (newrepos, repository);
  555.     }
  556.  
  557.     /* call-back dir entry proc (if any) */
  558.     if (direntproc != NULL)
  559.     dir_return = direntproc (dir, newrepos, update_dir);
  560.  
  561.     /* only process the dir if the return code was 0 */
  562.     if (dir_return != R_SKIP_ALL)
  563.     {
  564.     /* save our current directory and static vars */
  565.         if (save_cwd (&cwd))
  566.         exit (EXIT_FAILURE);
  567.     sdirlist = dirlist;
  568.     srepository = repository;
  569.     dirlist = NULL;
  570.  
  571.     /* cd to the sub-directory */
  572.     if (chdir (dir) < 0)
  573.         error (1, errno, "could not chdir to %s", dir);
  574.  
  575.     /* honor the global SKIP_DIRS (a.k.a. local) */
  576.     if (flags == R_SKIP_DIRS)
  577.         dir_return = R_SKIP_DIRS;
  578.  
  579.     /* remember if the `.' will be stripped for subsequent dirs */
  580.     if (strcmp (update_dir, ".") == 0)
  581.     {
  582.         update_dir[0] = '\0';
  583.         stripped_dot = 1;
  584.     }
  585.  
  586.     /* make the recursive call */
  587.     err += do_recursion (fileproc, filesdoneproc, direntproc, dirleaveproc,
  588.                 dir_return, which, aflag, readlock, dosrcs);
  589.  
  590.     /* put the `.' back if necessary */
  591.     if (stripped_dot)
  592.         (void) strcpy (update_dir, ".");
  593.  
  594.     /* call-back dir leave proc (if any) */
  595.     if (dirleaveproc != NULL)
  596.         err = dirleaveproc (dir, err, update_dir);
  597.  
  598.     /* get back to where we started and restore state vars */
  599.     if (restore_cwd (&cwd, NULL))
  600.         exit (EXIT_FAILURE);
  601.     free_cwd (&cwd);
  602.     dirlist = sdirlist;
  603.     repository = srepository;
  604.     }
  605.  
  606.     /* put back update_dir */
  607.     cp = last_component (update_dir);
  608.     if (cp > update_dir)
  609.     cp[-1] = '\0';
  610.     else
  611.     update_dir[0] = '\0';
  612.  
  613.     return (err);
  614. }
  615.  
  616. /*
  617.  * Add a node to a list allocating the list if necessary.
  618.  */
  619. static void
  620. addlist (listp, key)
  621.     List **listp;
  622.     char *key;
  623. {
  624.     Node *p;
  625.  
  626.     if (*listp == NULL)
  627.     *listp = getlist ();
  628.     p = getnode ();
  629.     p->type = FILES;
  630.     p->key = xstrdup (key);
  631.     if (addnode (*listp, p) != 0)
  632.     freenode (p);
  633. }
  634.  
  635. static void
  636. addfile (listp, dir, file)
  637.     List **listp;
  638.     char *dir;
  639.     char *file;
  640. {
  641.     Node *n;
  642.  
  643.     /* add this dir. */
  644.     addlist (listp, dir);
  645.  
  646.     n = findnode (*listp, dir);
  647.     if (n == NULL)
  648.     {
  649.     error (1, 0, "can't find recently added dir node `%s' in start_recursion.",
  650.            dir);
  651.     }
  652.  
  653.     n->type = DIRS;
  654.     addlist ((List **) &n->data, file);
  655.     return;
  656. }
  657.  
  658. static int
  659. unroll_files_proc (p, closure)
  660.     Node *p;
  661.     void *closure;
  662. {
  663.     Node *n;
  664.     struct recursion_frame *frame = (struct recursion_frame *) closure;
  665.     int err = 0;
  666.     List *save_dirlist;
  667.     char *save_update_dir = NULL;
  668.     struct saved_cwd cwd;
  669.  
  670.     /* if this dir was also an explicitly named argument, then skip
  671.        it.  We'll catch it later when we do dirs. */
  672.     n = findnode (dirlist, p->key);
  673.     if (n != NULL)
  674.     return (0);
  675.  
  676.     /* otherwise, call dorecusion for this list of files. */
  677.     filelist = (List *) p->data;
  678.     save_dirlist = dirlist;
  679.     dirlist = NULL;
  680.  
  681.     if (strcmp(p->key, ".") != 0)
  682.     {
  683.         if (save_cwd (&cwd))
  684.         exit (EXIT_FAILURE);
  685.     if (chdir (p->key) < 0)
  686.         error (1, errno, "could not chdir to %s", p->key);
  687.  
  688.     save_update_dir = xstrdup (update_dir);
  689.  
  690.     if (*update_dir != '\0')
  691.         (void) strcat (update_dir, "/");
  692.  
  693.     (void) strcat (update_dir, p->key);
  694.     }
  695.  
  696.     err += do_recursion (frame->fileproc, frame->filesdoneproc,
  697.              frame->direntproc, frame->dirleaveproc,
  698.              frame->flags, frame->which, frame->aflag,
  699.              frame->readlock, frame->dosrcs);
  700.  
  701.     if (save_update_dir != NULL)
  702.     {
  703.     (void) strcpy (update_dir, save_update_dir);
  704.     free (save_update_dir);
  705.  
  706.     if (restore_cwd (&cwd, NULL))
  707.         exit (EXIT_FAILURE);
  708.     free_cwd (&cwd);
  709.     }
  710.  
  711.     dirlist = save_dirlist;
  712.     filelist = NULL;
  713.     return(err);
  714. }
  715.